using System;
using System.Collections.Generic;
using System.Text;
using TTDotNet.Util;
using TTDotNet.Engine.Terrain;

namespace TTDotNet.Engine.Networks
{
    public class RoadNetwork
    {
        private Dictionary<DDDCoordinate, RoadNode> nodes;
        private Dictionary<DDDCoordinate, List<RoadPath>> paths;

        public RoadNetwork()
        {
            nodes = new Dictionary<DDDCoordinate, RoadNode>();
            paths = new Dictionary<DDDCoordinate, List<RoadPath>>();
        }

        public bool AddRoad(List<DDDCoordinate> coordsAdded)
        {
            RotateType rotType = RotateType.Zero;

            int last = coordsAdded.Count - 1;

            if (coordsAdded[0].X < coordsAdded[last].X)
            {
                rotType = RotateType.Ninety;
            }
            else if (coordsAdded[0].X > coordsAdded[last].X)
            {
                rotType = RotateType.TwoSeventy;
            }
            if (coordsAdded[0].Z < coordsAdded[last].Z)
            {
                rotType = RotateType.Zero;
            }
            else if (coordsAdded[0].Z > coordsAdded[last].Z)
            {
                rotType = RotateType.OneEighty;
            }

            RotateType oppRot = (RotateType)(((int)rotType + 2) % 4);

            foreach (DDDCoordinate c in coordsAdded)
            {
                if (coordsAdded.IndexOf(c) == 0)
                {
                    if (!(nodes.ContainsKey(c)))
                    {
                        CreateNode(c, rotType);
                    }
                    else
                    {
                        AddToNode(c, rotType);
                    }

                }
                else if (coordsAdded.IndexOf(c) == (coordsAdded.Count - 1))
                {
                    if (!(nodes.ContainsKey(c)))
                    {
                        CreateNode(c, oppRot);
                    }
                    else
                    {
                        AddToNode(c, oppRot);
                    }

                }
                else
                {
                    if (!(nodes.ContainsKey(c)))
                    {
                        CreateNode(c, oppRot);
                    }
                    else
                    {
                        AddToNode(c, oppRot);
                    }

                    if (!(nodes.ContainsKey(c)))
                    {
                        CreateNode(c, rotType);
                    }
                    else
                    {
                        AddToNode(c, rotType);
                    }

                }
                
            }

            return true;

        }

        private void PrintAllRotates()
        {
            foreach (RoadNode node in nodes.Values)
            {
                System.Windows.Forms.MessageBox.Show(node.GetCoord() + " is a " + node.GetRotateAngle() + " and = " + node.GetRoadType());
            }
        }

        private void PrintAllPaths()
        {
            System.Windows.Forms.MessageBox.Show("Here are all the paths:");

            foreach (List<RoadPath> rps in paths.Values)
            {
                foreach (RoadPath rp in rps)
                {
                    System.Windows.Forms.MessageBox.Show(rp + "");
                }
            }
        }

        private bool CreateNode(DDDCoordinate coord, RotateType rotType)
        {
            if (!(IsAlreadyCovered(coord, rotType)))
            {
                RoadNode newNode = new RoadNode(coord, rotType);
                nodes.Add(newNode.GetCoord(), newNode);

                DDDCoordinate neighbour = GetAdjacentTile(coord, rotType);

                if (!(NodeCollectionContains(nodes.Keys, neighbour)))
                {
                    return true;
                }
                else 
                {
                    ConnectNode(coord, rotType, neighbour);
                }
                
                return true;
            }

            return false ;


        }

        private void ConnectNode(DDDCoordinate coord, RotateType rotType, DDDCoordinate neighbour)
        {
            if (StraightRoad(neighbour))
            {
                List<RoadPath> rps = GetPathsTo(neighbour);

                if (rps.Count == 0)
                {
                    if (CanConnect(coord,neighbour))
                    {
                        BuildPath(coord, neighbour);
                    }
                }
                else if (rps.Count == 1)
                {
                    if (CanConnect(coord, neighbour))
                    {
                        RemovePathsToFrom(neighbour);
                        BuildPath(coord, rps[0].GetSource().GetCoord());
                    }
                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("OH DEAR");
                }
            }
            else
            {
                
                if(CanConnect(coord, neighbour)) {
                    BuildPath(coord, neighbour);
                }
            }
        }

        private bool CanConnect(DDDCoordinate coord, DDDCoordinate otherCoord)
        {
            RoadNode first = nodes[coord];
            RoadNode other = nodes[otherCoord];

            return first.CanConnectTo(other);
        }

        private bool AddToNode(DDDCoordinate coord, RotateType rotType)
        {
            DDDCoordinate neighbour = GetAdjacentTile(coord, rotType);

            TileType before = nodes[coord].GetRoadType();

            if (nodes[coord].GetRotTypes().Contains(rotType))
            {
                return false;
            }
            else
            {
                nodes[coord].AddToNode(rotType);
            }

            if (before == TileType.RoadStraight && nodes[coord].GetRoadType() == TileType.RoadJunction)
            {
                List<RoadPath> containedIn = GetPathsContaining(coord);

                if (containedIn.Count != 2)
                {
                    System.Windows.Forms.MessageBox.Show("PROBLEM! contained count > 2, coord = " + coord);

                    foreach (RoadPath rp in containedIn)
                    {
                        System.Windows.Forms.MessageBox.Show(coord + " is contained in " + rp);
                    }
                }

                DDDCoordinate start = containedIn[0].GetSource().GetCoord();
                DDDCoordinate end = containedIn[0].GetDest().GetCoord();

                RemovePathsContaining(coord);

                BuildPath(start, coord);
                BuildPath(coord, end);
            }


            if (StraightRoad(coord))
            {
                if (NodeCollectionContains(nodes.Keys, neighbour))
                {
                    if (CanConnect(coord, neighbour))
                    {
                        if (StraightRoad(neighbour))
                        {
                            List<RoadPath> fromCoord = GetPathsFrom(coord);
                            List<RoadPath> toCoord = GetPathsTo(coord);
                            List<RoadPath> fromNeighbour = GetPathsFrom(neighbour);
                            List<RoadPath> toNeighbour = GetPathsTo(neighbour);

                            if (fromCoord.Count != 1 || toCoord.Count != 1 || fromNeighbour.Count != 1 || toNeighbour.Count != 1)
                            {
                                System.Windows.Forms.MessageBox.Show("Problem...");
                            }
                            else
                            {
                                DDDCoordinate neighbourPathStart = toNeighbour[0].GetSource().GetCoord();
                                DDDCoordinate coordPathStart = toCoord[0].GetSource().GetCoord();

                                RemovePathsToFrom(coord);
                                RemovePathsToFrom(neighbour);

                                BuildPath(coordPathStart, neighbourPathStart);
                            }
                        }
                        else if (HalfRoad(neighbour))
                        {
                            List<RoadPath> toCoord = GetPathsTo(coord);

                            if (toCoord.Count != 1)
                            {
                                System.Windows.Forms.MessageBox.Show("Problem!");

                                foreach (RoadPath rp in toCoord)
                                {
                                    System.Windows.Forms.MessageBox.Show("Error: " + rp.ToString());
                                }
                            }
                            else
                            {
                                DDDCoordinate neighbourPathStart = neighbour;
                                DDDCoordinate coordPathStart = toCoord[0].GetSource().GetCoord();

                                RemovePathsToFrom(coord);
                                RemovePathsToFrom(neighbour); //Sanity

                                BuildPath(coordPathStart, neighbourPathStart);

                            }
                            
                        }
                    }
                }
            }
            else
            {
                if (NodeCollectionContains(nodes.Keys, neighbour))
                {
                    ConnectNode(coord, rotType, neighbour);
                }
            }

            return true;
        }

        private DDDCoordinate GetAdjacentTile(DDDCoordinate coord, RotateType rotType)
        {
            DDDCoordinate adjacentTile = new DDDCoordinate(coord.X, coord.Y, coord.Z);

            if (rotType == RotateType.Zero) { adjacentTile.Z = adjacentTile.Z + 1; }
            else if (rotType == RotateType.Ninety) { adjacentTile.X = adjacentTile.X + 1; }
            else if (rotType == RotateType.OneEighty) { adjacentTile.Z = adjacentTile.Z - 1; }
            else { adjacentTile.X = adjacentTile.X - 1; }

            return adjacentTile;
        }

        private bool StraightRoad(DDDCoordinate coord)
        {
            if(NodeCollectionContains(nodes.Keys, coord)) {
                return (nodes[coord].GetRoadType() == TileType.RoadStraight);
            }
            return false;
        }

        private bool HalfRoad(DDDCoordinate coord)
        {
            if (NodeCollectionContains(nodes.Keys, coord))
            {
                return (nodes[coord].GetRoadType() == TileType.RoadHalf);
            }
            return false;
        }

        private bool JunctionRoad(DDDCoordinate coord)
        {
            if (NodeCollectionContains(nodes.Keys, coord))
            {
                return (nodes[coord].GetRoadType() == TileType.RoadJunction || nodes[coord].GetRoadType() == TileType.RoadCrossroads);
            }
            return false;
        }

        private List<DDDCoordinate> CoordNeighbours(Dictionary<DDDCoordinate, RoadNode>.KeyCollection keyColl, DDDCoordinate coord)
        {
            List<DDDCoordinate> potentials = new List<DDDCoordinate>();
            potentials.Add(new DDDCoordinate(coord.X + 1, coord.Y, coord.Z));
            potentials.Add(new DDDCoordinate(coord.X - 1, coord.Y, coord.Z));
            potentials.Add(new DDDCoordinate(coord.X, coord.Y, coord.Z + 1));
            potentials.Add(new DDDCoordinate(coord.X, coord.Y, coord.Z - 1));

            List<DDDCoordinate> certains = new List<DDDCoordinate>();

            foreach (DDDCoordinate c in keyColl)
            {
                if (potentials.Contains(c))
                {
                    certains.Add(c);
                }
            }

            return certains;
        }

        private bool NodeCollectionContains(Dictionary<DDDCoordinate, RoadNode>.KeyCollection keyColl, DDDCoordinate coord)
        {
            foreach (DDDCoordinate c in keyColl)
            {
                if (coord.Equals(c)) return true;
            }
            return false;
        }

        private bool PathCollectionContains(Dictionary<DDDCoordinate, List<RoadPath>>.KeyCollection keyColl, DDDCoordinate coord)
        {
            foreach (DDDCoordinate c in keyColl)
            {
                if (coord.Equals(c)) return true;
            }
            return false;
        }

        private bool IsAlreadyCovered(DDDCoordinate coord, RotateType rotType)
        {
            bool result = false;

            if (rotType == RotateType.Zero) { result = SquareCovered(coord, RotateType.Zero, Axis.Z); }
            else if (rotType == RotateType.Ninety) { result = SquareCovered(coord, RotateType.Ninety, Axis.X); }
            else if (rotType == RotateType.OneEighty) { result = SquareCovered(coord, RotateType.OneEighty, Axis.Z); }
            else if (rotType == RotateType.TwoSeventy) { result = SquareCovered(coord, RotateType.TwoSeventy, Axis.X); }

            return result;
        }

        private bool SquareCovered(DDDCoordinate coord, RotateType rotType, Axis axis)
        {
            foreach (DDDCoordinate c in nodes.Keys)
            {
                if (c.Equals(coord))
                {
                    foreach (RoadPath rp in paths[c])
                    {

                    }
                }
            }
            return false;
        }

        private void RemovePathsToFrom(DDDCoordinate coord)
        {
            List<RoadPath> list = GetPathsFrom(coord);

            foreach (RoadPath rp in list)
            {
                List<RoadPath> opps = GetPathsFrom(rp.GetDest().GetCoord());

                foreach (RoadPath rp2 in opps)
                {
                    if (rp2.GetDest().GetCoord().Equals(coord))
                    {
                        paths[rp2.GetSource().GetCoord()].Remove(rp2);
                    }
                }
            }
            paths.Remove(coord);
        }

        private void RemovePathsContaining(DDDCoordinate coord)
        {
            List<RoadPath> pathsToRemove = new List<RoadPath>();

            foreach (List<RoadPath> rps in paths.Values)
            {

                foreach(RoadPath rp in rps) {
                    if (coord.IsBetween(rp.GetSource().GetCoord(), rp.GetDest().GetCoord()))
                    {
                        pathsToRemove.Add(rp);
                    }
                }
                
            }

            foreach (RoadPath path in pathsToRemove)
            {
                paths[path.GetSource().GetCoord()].Remove(path);
            }

        }

        public bool RemoveSquare(DDDCoordinate coord)
        {
            nodes.Remove(coord);
            paths.Remove(coord);

            List<RoadPath> pathsTo = GetPathsTo(coord);

            foreach (RoadPath rp in pathsTo)
            {
                System.Windows.Forms.MessageBox.Show("Shortening: " + rp);
                ShortenPath(rp);
            }


            System.Windows.Forms.MessageBox.Show("Current paths are: ");
            foreach (List<RoadPath> rps in paths.Values)
            {
                foreach (RoadPath rp in rps)
                {
                    System.Windows.Forms.MessageBox.Show(rp.ToString());
                }
            }

            return true;
        }

        private List<RoadPath> GetPathsTo(DDDCoordinate coord)
        {
            List<RoadPath> pathsTo = new List<RoadPath>();

            foreach (List<RoadPath> rps in paths.Values)
            {
                foreach(RoadPath rp in rps)
                    if (rp.GetDest().GetCoord().Equals(coord)) pathsTo.Add(rp);
            }

            return pathsTo;
        }

        private List<RoadPath> GetPathsFrom(DDDCoordinate coord)
        {
            List<RoadPath> pathsFrom = new List<RoadPath>();

            foreach (List<RoadPath> rps in paths.Values)
            {
                foreach(RoadPath rp in rps)
                    if (rp.GetSource().GetCoord().Equals(coord)) pathsFrom.Add(rp);
            }

            return pathsFrom;
        }

        private List<RoadPath> GetPathsContaining(DDDCoordinate coord)
        {
            List<RoadPath> pathsContaining = new List<RoadPath>();

            foreach (List<RoadPath> rps in paths.Values)
            {
                foreach (RoadPath rp in rps)
                {
                    if (coord.IsBetween(rp.GetSource().GetCoord(), rp.GetDest().GetCoord()))
                    {
                        pathsContaining.Add(rp);
                    }
                }
            }

            return pathsContaining;
        }

        private bool BuildPath(DDDCoordinate c1, DDDCoordinate c2)
        {
            RoadPath oneWay;
            RoadPath twoWay;

            oneWay = new RoadPath(nodes[c1], nodes[c2]);
            twoWay = new RoadPath(nodes[c2], nodes[c1]);

            if (PathCollectionContains(paths.Keys, c1))
            {
                List<RoadPath> curr = paths[c1];
                curr.Add(oneWay);
            }
            else
            {
                List<RoadPath> newList = new List<RoadPath>();
                newList.Add(oneWay);
                paths[c1] = newList;
            }

            if (PathCollectionContains(paths.Keys, c2))
            {
                List<RoadPath> curr = paths[c2];
                curr.Add(twoWay);
            }
            else
            {
                List<RoadPath> newList = new List<RoadPath>();
                newList.Add(twoWay);
                paths[c2] = newList;
            }

            return true;
        }

        public TileType GetRoadType(DDDCoordinate coord)
        {
            return nodes[coord].GetRoadType();
        }

        public RotateType GetRotateType(DDDCoordinate coord)
        {
            return nodes[coord].GetRotateAngle();
        }

        public void ShortenPath(RoadPath rp)    
        {
            int xChange = 0;
            int zChange = 0;

            DDDCoordinate s = rp.GetSource().GetCoord();
            DDDCoordinate d = rp.GetDest().GetCoord();

            if (s.X < d.X) xChange = -1;
            else if (s.X > d.X) xChange = 1;
            else if (s.Z < d.Z) zChange = -1;
            else if (s.Z > d.Z) zChange = 1;
    
            DDDCoordinate newD = new DDDCoordinate(d.X + xChange, d.Y, d.Z + zChange); // Height...?
            RotateType rot = RotateType.Zero;

            if(xChange != 0) rot = RotateType.Ninety;
            else { rot = RotateType.Zero; }

            RoadNode newNode = new RoadNode(newD, rot);

            if(nodes.ContainsKey(newNode.GetCoord())) {
                paths[newNode.GetCoord()].Remove(rp);
            }
            else{
                rp.SetDest(newNode);
            }
        }

        public DDDCoordinate AStarPathFind(DDDCoordinate from, DDDCoordinate to)
        {
            if (from.Equals(to)) return from;

            AStarNode startNode = new AStarNode(null, from, 0, from.GridDistanceTo(to));

            if (!(NodeCollectionContains(nodes.Keys, from))) return from;

            if (!(NodeCollectionContains(nodes.Keys, to))) return from;

            List<AStarNode> solution = new List<AStarNode>();

            SortedNodeList openList = new SortedNodeList();
            List<AStarNode> closedList = new List<AStarNode>();

            openList.AddNode(startNode);

            while (openList.Size() > 0)
            {
                AStarNode current = openList.Pop();

                if (current.IsGoal(to))
                {
                    while (current != null)
                    {
                        solution.Insert(0, current);
                        current = current.GetParent();
                    }
                    break;
                }

                List<AStarNode> successors = GetDescendants(current, to);

                foreach (AStarNode node in successors)
                {
                    AStarNode openNode = null;
                    if (openList.Contains(node)) openNode = openList.Fetch(node);
                    if ((openNode != null) && (node.GetTotalCost() > openNode.GetTotalCost())) continue;


                    AStarNode closedNode = null;
                    if (closedList.Contains(node)) closedNode = closedList[closedList.IndexOf(node)];
                    if ((closedNode != null) && (node.GetTotalCost() > closedNode.GetTotalCost())) continue;

                    openList.RemoveNode(openNode);
                    closedList.Remove(closedNode);

                    openList.AddNode(node);

                }

                closedList.Add(current);

            }

            if (solution.Count > 0)
            {
                return new DDDCoordinate(solution[1].GetCoord().X, solution[1].GetCoord().Z);
            }
            else
            {
                return from;
            }
        }

        public List<AStarNode> GetDescendants(AStarNode source, DDDCoordinate target)
        {
            DDDCoordinate nodeCoord = source.GetCoord();
            List<RoadPath> descendantList = new List<RoadPath>();

            if (PathCollectionContains(paths.Keys, nodeCoord))
            {
                descendantList = paths[nodeCoord];
            }

            List<AStarNode> descendants = new List<AStarNode>();

            foreach (RoadPath rp in descendantList)
            {
                int costToHere = source.GetCostToHere() + rp.GetPathLength();
                int costToDestination = rp.GetDest().GetCoord().GridDistanceTo(target);

                AStarNode temp = new AStarNode(source, rp.GetDest().GetCoord(), costToHere, costToDestination);
                descendants.Add(temp);
            }
            
            return descendants;
        }

    }
}
